﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LightCAD.MathLib;
using static LightCAD.MathLib.Constants;

namespace LightCAD.Three
{

    public class WebGLMaterials
    {
        private WebGLRenderer renderer;
        private WebGLProperties properties;
        public WebGLMaterials(WebGLRenderer renderer, WebGLProperties properties)
        {
            this.renderer = renderer;
            this.properties = properties;
        }
        public void refreshTransformUniform(Texture map, Uniform uniform)
        {
            if (map.matrixAutoUpdate)
            {
                map.updateMatrix();
            }
            (uniform.value as Matrix3).Copy(map.matrix);

        }
        public void refreshFogUniforms(Uniforms uniforms, IFog fog)
        {

            fog.color.GetRGB(uniforms["fogColor"].value as Color, UniformsUtils.getUnlitUniformColorSpace(renderer));

            if (fog is Fog)
            {
                var _fog = fog as Fog;
                uniforms["fogNear"].value = _fog.near;
                uniforms["fogFar"].value = _fog.far;

            }
            else if (fog is FogExp2)
            {
                var _fog = fog as FogExp2;
                uniforms["fogDensity"].value = _fog.density;

            }
        }
        public void refreshMaterialUniforms(Uniforms uniforms, Material material, double pixelRatio, double height, WebGLRenderTarget transmissionRenderTarget)
        {

            if (material is MeshBasicMaterial)
            {

                refreshUniformsCommon(uniforms, material);

            }
            else if (material is MeshLambertMaterial)
            {

                refreshUniformsCommon(uniforms, material);

            }
            else if (material is MeshToonMaterial)
            {

                refreshUniformsCommon(uniforms, material);
                refreshUniformsToon(uniforms, material as MeshToonMaterial);

            }
            else if (material is MeshPhongMaterial)
            {

                refreshUniformsCommon(uniforms, material);
                refreshUniformsPhong(uniforms, material as MeshPhongMaterial);

            }
            else if (material is MeshStandardMaterial)
            {

                refreshUniformsCommon(uniforms, material);
                refreshUniformsStandard(uniforms, material as MeshStandardMaterial);

                if (material is MeshPhysicalMaterial)
                {

                    refreshUniformsPhysical(uniforms, material as MeshPhysicalMaterial, transmissionRenderTarget);

                }

            }
            else if (material is MeshMatcapMaterial)
            {

                refreshUniformsCommon(uniforms, material);
                refreshUniformsMatcap(uniforms, material as MeshMatcapMaterial);

            }
            else if (material is MeshDepthMaterial)
            {

                refreshUniformsCommon(uniforms, material);

            }
            else if (material is MeshDistanceMaterial)
            {

                refreshUniformsCommon(uniforms, material);
                refreshUniformsDistance(uniforms, material as MeshDistanceMaterial);

            }
            else if (material is MeshNormalMaterial)
            {

                refreshUniformsCommon(uniforms, material);

            }
            else if (material is LineBasicMaterial)
            {

                refreshUniformsLine(uniforms, material as LineBasicMaterial);

                if (material is LineDashedMaterial)
                {

                    refreshUniformsDash(uniforms, material as LineDashedMaterial);

                }

            }
            else if (material is PointsMaterial)
            {

                refreshUniformsPoints(uniforms, material as PointsMaterial, pixelRatio, height);

            }
            else if (material is SpriteMaterial)
            {

                refreshUniformsSprites(uniforms, material as SpriteMaterial);

            }
            else if (material is ShadowMaterial)
            {

                (uniforms.color.value as Color).Copy((material as ShadowMaterial).color);
                uniforms.opacity.value = material.opacity;

            }
            else if (material is ShaderMaterial)
            {

                (material as ShaderMaterial).uniformsNeedUpdate = false; // #15581

            }

        }

        public void refreshUniformsCommon(Uniforms uniforms, Material material)
        {
            uniforms.opacity.value = material.opacity;
            var matColor = material.color;
            if (matColor != null && uniforms.diffuse != null)
            {
                (uniforms.diffuse.value as Color).Copy(matColor);
            }

            var matEmissive = material.emissive;
            if (matEmissive != null && uniforms.emissive != null)
            {
                (uniforms.emissive.value as Color).Copy(matEmissive).MultiplyScalar(material.emissiveIntensity);
            }

            var matMap = material.map;
            if (matMap != null && uniforms.map != null)
            {
                uniforms.map.value = matMap;
                refreshTransformUniform(material.map, uniforms.mapTransform);
            }

            var matAlphaMap = material.alphaMap;
            if (matAlphaMap != null && uniforms.alphaMap != null)
            {
                uniforms.alphaMap.value = matAlphaMap;
                refreshTransformUniform(material.alphaMap, uniforms.alphaMapTransform);
            }

            var matBumpMap = material.bumpMap;
            if (matBumpMap != null && uniforms.bumpMap != null)
            {
                uniforms.bumpMap.value = matBumpMap;
                refreshTransformUniform(material.bumpMap, uniforms.bumpMapTransform);
                uniforms.bumpScale.value = material.bumpScale;
                if (material.side == BackSide)
                    uniforms.bumpScale.value = -1 * (double)uniforms.bumpScale.value;

            }

            if (material.normalMap != null)
            {
                uniforms.normalMap.value = material.normalMap;
                refreshTransformUniform(material.normalMap, uniforms.normalMapTransform);
                (uniforms.normalScale.value as Vector2).Copy(material.normalScale);
                if (material.side == BackSide)
                {
                    (uniforms.normalScale.value as Vector2).Negate();
                }
            }
            var matDisplacementMap = material.displacementMap;
            if (matDisplacementMap != null && uniforms.displacementMap != null)
            {
                uniforms.displacementMap.value = matDisplacementMap;
                refreshTransformUniform(material.displacementMap, uniforms.displacementMapTransform);
                uniforms.displacementScale.value = material.displacementScale;
                uniforms.displacementBias.value = material.displacementBias;

            }

            var matEmissiveMap = material.emissiveMap;
            if (matEmissiveMap != null)
            {
                uniforms.emissiveMap.value = matEmissiveMap;
                refreshTransformUniform(material.emissiveMap, uniforms.emissiveMapTransform);
            }


            var matSpecularMap = material.specularMap;
            if (matSpecularMap != null)
            {
                uniforms.specularMap.value = matSpecularMap;
                refreshTransformUniform(material.specularMap, uniforms.specularMapTransform);
            }

            if (material.alphaTest > 0)
            {
                uniforms.alphaTest.value = material.alphaTest;
            }

            var envMap = properties.get(material).envMap as Texture;

            if (envMap != null)
            {

                uniforms.envMap.value = envMap;

                uniforms.flipEnvMap.value = (envMap is CubeTexture && envMap.isRenderTargetTexture == false) ? -1 : 1;

                uniforms.reflectivity.value = material.reflectivity;
                uniforms.ior.value = material.ior;
                uniforms.refractionRatio.value = material.refractionRatio;

            }

            var matLightMap = material.lightMap;
            if (matLightMap != null)
            {
                uniforms.lightMap.value = matLightMap;
                // artist-friendly light intensity scaling factor
                var scaleFactor = (renderer.useLegacyLights) ? Math.PI : 1;
                refreshTransformUniform(material.lightMap, uniforms.lightMapTransform);
                uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor;

            }
            var matAoMap = material.aoMap;
            if (matAoMap != null)
            {
                uniforms.aoMap.value = matAoMap;
                uniforms.aoMapIntensity.value = material.aoMapIntensity;
                refreshTransformUniform(material.aoMap, uniforms.aoMapTransform);
            }
            /*r151删除了以下代码 */
            // uv repeat and offset setting priorities
            // 1. color map
            // 2. specular map
            // 3. displacementMap map
            // 4. normal map
            // 5. bump map
            // 6. roughnessMap map
            // 7. metalnessMap map
            // 8. alphaMap map
            // 9. emissiveMap map
            // 10. clearcoat map
            // 11. clearcoat normal map
            // 12. clearcoat roughnessMap map
            // 13. iridescence map
            // 14. iridescence thickness map
            // 15. specular intensity map
            // 16. specular tint map
            // 17. transmission map
            // 18. thickness map

            //object uvScaleMap = null;
            //if (matMap != null)
            //{
            //    uvScaleMap = matMap;
            //}
            //else if (matSpecularMap != null)
            //{
            //    uvScaleMap = matSpecularMap;
            //}
            //else if (matDisplacementMap != null)
            //{
            //    uvScaleMap = matDisplacementMap;
            //}
            //else if (matNormalMap != null)
            //{

            //    uvScaleMap = matNormalMap;

            //}
            //else if (matBumpMap != null)
            //{

            //    uvScaleMap = matBumpMap;

            //}
            //else if (material.roughnessMap != null)
            //{

            //    uvScaleMap = material.roughnessMap;

            //}
            //else if (material.metalnessMap != null)
            //{

            //    uvScaleMap = material.metalnessMap;

            //}
            //else if (matAlphaMap != null)
            //{
            //    uvScaleMap = matAlphaMap;
            //}
            //else if (matEmissiveMap != null)
            //{
            //    uvScaleMap = matEmissiveMap;
            //}
            //else if (material.clearcoatMap != null)
            //{

            //    uvScaleMap = material.clearcoatMap;

            //}
            //else if (material.clearcoatNormalMap != null)
            //{

            //    uvScaleMap = material.clearcoatNormalMap;

            //}
            //else if (material.clearcoatRoughnessMap != null)
            //{

            //    uvScaleMap = material.clearcoatRoughnessMap;

            //}
            //else if (material.iridescenceMap != null)
            //{

            //    uvScaleMap = material.iridescenceMap;

            //}
            //else if (material.iridescenceThicknessMap != null)
            //{

            //    uvScaleMap = material.iridescenceThicknessMap;

            //}
            //else if (material.specularIntensityMap != null)
            //{

            //    uvScaleMap = material.specularIntensityMap;

            //}
            //else if (material.specularColorMap != null)
            //{

            //    uvScaleMap = material.specularColorMap;

            //}
            //else if (material.transmissionMap != null)
            //{

            //    uvScaleMap = material.transmissionMap;

            //}
            //else if (material.thicknessMap != null)
            //{

            //    uvScaleMap = material.thicknessMap;

            //}
            //else if (material.sheenColorMap != null)
            //{

            //    uvScaleMap = material.sheenColorMap;

            //}
            //else if (material.sheenRoughnessMap != null)
            //{

            //    uvScaleMap = material.sheenRoughnessMap;

            //}

            //if (uvScaleMap != null)
            //{

            //    // backwards compatibility
            //    if (uvScaleMap is WebGLRenderTarget)
            //    {

            //        uvScaleMap = (uvScaleMap as WebGLRenderTarget).texture;

            //    }

            //    if ((uvScaleMap as Texture).matrixAutoUpdate)
            //    {

            //        (uvScaleMap as Texture).updateMatrix();

            //    }

            //    (uniforms.uvTransform.value as Matrix3).copy((uvScaleMap as Texture).matrix);

            //}

            // uv repeat and offset setting priorities for uv2
            // 1. ao map
            // 2. light map

            //object uv2ScaleMap = null;

            //if (matAoMap != null)
            //    uv2ScaleMap = matAoMap;
            //else if (matLightMap != null)
            //    uv2ScaleMap = matLightMap;

            //if (uv2ScaleMap != null)
            //{

            //    // backwards compatibility
            //    if (uv2ScaleMap is WebGLRenderTarget)
            //    {

            //        uv2ScaleMap = (uv2ScaleMap as WebGLRenderTarget).texture;

            //    }

            //    if ((uv2ScaleMap as Texture).matrixAutoUpdate)
            //    {

            //        (uv2ScaleMap as Texture).updateMatrix();

            //    }

            //    (uniforms.uv2Transform.value as Matrix3).copy((uv2ScaleMap as Texture).matrix);
            //}
        }

        public void refreshUniformsLine(Uniforms uniforms, LineBasicMaterial material)
        {
            (uniforms.diffuse.value as Color).Copy(material.color);
            uniforms.opacity.value = material.opacity;
            if (material.map != null)
            {
                uniforms.map.value = material.map;
                refreshTransformUniform(material.map, uniforms.mapTransform);
            }
        }

        public void refreshUniformsDash(Uniforms uniforms, LineDashedMaterial material)
        {
            uniforms["dashSize"].value = material.dashSize;
            uniforms["totalSize"].value = material.dashSize + material.gapSize;
            uniforms["scale"].value = material.scale;
        }
        public void refreshUniformsPoints(Uniforms uniforms, PointsMaterial material, double pixelRatio, double height)
        {

            (uniforms.diffuse.value as Color).Copy(material.color);
            uniforms.opacity.value = material.opacity;
            uniforms.size.value = material.size * pixelRatio;
            uniforms.scale.value = height * 0.5;

            if (material.map != null)
            {
                uniforms.map.value = material.map;
                refreshTransformUniform(material.map, uniforms.uvTransform);
            }

            if (material.alphaMap != null)
            {

                uniforms.alphaMap.value = material.alphaMap;
            }

            if (material.alphaTest > 0)
            {

                uniforms.alphaTest.value = material.alphaTest;

            }
        }

        public void refreshUniformsSprites(Uniforms uniforms, SpriteMaterial material)
        {

            (uniforms.diffuse.value as Color).Copy(material.color);
            uniforms.opacity.value = material.opacity;
            uniforms.rotation.value = material.rotation;

            if (material.map != null)
            {
                uniforms.map.value = material.map;
                refreshTransformUniform(material.map, uniforms.mapTransform);
            }

            if (material.alphaMap != null)
            {

                uniforms.alphaMap.value = material.alphaMap;
            }

            if (material.alphaTest > 0)
            {

                uniforms.alphaTest.value = material.alphaTest;

            }
            /* r151 删除以下代码
            // uv repeat and offset setting priorities
            // 1. color map
            // 2. alpha map
            Texture uvScaleMap = null;

            if (material.map != null)
            {

                uvScaleMap = material.map;

            }
            else if (material.alphaMap != null)
            {

                uvScaleMap = material.alphaMap;

            }

            if (uvScaleMap != null)
            {

                if (uvScaleMap.matrixAutoUpdate)
                {

                    uvScaleMap.updateMatrix();

                }

                (uniforms.uvTransform.value as Matrix3).copy(uvScaleMap.matrix);

            }
            */
        }

        public void refreshUniformsPhong(Uniforms uniforms, MeshPhongMaterial material)
        {

            (uniforms.specular.value as Color).Copy(material.specular);
            uniforms.shininess.value = Math.Max(material.shininess, 1e-4); // to prevent pow( 0.0, 0.0 )

        }

        public void refreshUniformsToon(Uniforms uniforms, MeshToonMaterial material)
        {

            if (material.gradientMap != null)
            {

                uniforms.gradientMap.value = material.gradientMap;

            }
        }

        public void refreshUniformsStandard(Uniforms uniforms, MeshStandardMaterial material)
        {
            uniforms.metalness.value = material.metalness;

            if (material.metalnessMap != null)
            {

                uniforms.metalnessMap.value = material.metalnessMap;

                refreshTransformUniform(material.metalnessMap, uniforms.metalnessMapTransform);

            }

            uniforms.roughness.value = material.roughness;

            if (material.roughnessMap != null)
            {

                uniforms.roughnessMap.value = material.roughnessMap;

                refreshTransformUniform(material.roughnessMap, uniforms.roughnessMapTransform);

            }

            var envMap = properties.get(material).envMap as Texture;

            if (envMap != null)
            {

                //uniforms.envMap.value = material.envMap; // part of uniforms common
                uniforms.envMapIntensity.value = material.envMapIntensity;

            }
        }

        public void refreshUniformsPhysical(Uniforms uniforms, MeshPhysicalMaterial material, WebGLRenderTarget transmissionRenderTarget)
        {

            uniforms.ior.value = material.ior; // also part of uniforms common

            if (material.sheen > 0)
            {
                (uniforms.sheenColor.value as Color).Copy(material.sheenColor).MultiplyScalar(material.sheen);
                uniforms.sheenRoughness.value = material.sheenRoughness;

                if (material.sheenColorMap != null)
                {
                    uniforms.sheenColorMap.value = material.sheenColorMap;
                    refreshTransformUniform(material.sheenColorMap, uniforms.sheenColorMapTransform);
                }

                if (material.sheenRoughnessMap != null)
                {
                    uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap;
                    refreshTransformUniform(material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform);
                }

            }

            if (material.clearcoat > 0)
            {

                uniforms.clearcoat.value = material.clearcoat;
                uniforms.clearcoatRoughness.value = material.clearcoatRoughness;

                if (material.clearcoatMap != null)
                {
                    uniforms.clearcoatMap.value = material.clearcoatMap;
                    refreshTransformUniform(material.clearcoatMap, uniforms.clearcoatMapTransform);
                }

                if (material.clearcoatRoughnessMap != null)
                {
                    uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
                    refreshTransformUniform(material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform);
                }

                if (material.clearcoatNormalMap != null)
                {
                    uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;

                    refreshTransformUniform(material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform);

                    (uniforms.clearcoatNormalScale.value as Vector2).Copy(material.clearcoatNormalScale);

                    if (material.side == BackSide)
                    {

                        (uniforms.clearcoatNormalScale.value as Vector2).Negate();

                    }

                }

            }

            if (material.iridescence > 0)
            {

                uniforms.iridescence.value = material.iridescence;
                uniforms.iridescenceIOR.value = material.iridescenceIOR;
                uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[0];
                uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[1];

                if (material.iridescenceMap != null)
                {
                    uniforms.iridescenceMap.value = material.iridescenceMap;
                    refreshTransformUniform(material.iridescenceMap, uniforms.iridescenceMapTransform);
                }

                if (material.iridescenceThicknessMap != null)
                {
                    uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap;
                    refreshTransformUniform(material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform);
                }

            }

            if (material.transmission > 0)
            {

                uniforms.transmission.value = material.transmission;
                uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture;
                (uniforms.transmissionSamplerSize.value as Vector2).Set(transmissionRenderTarget.width, transmissionRenderTarget.height);

                if (material.transmissionMap != null)
                {
                    uniforms.transmissionMap.value = material.transmissionMap;
                    refreshTransformUniform(material.transmissionMap, uniforms.transmissionMapTransform);

                }
                uniforms.thickness.value = material.thickness;

                if (material.thicknessMap != null)
                {
                    uniforms.thicknessMap.value = material.thicknessMap;
                    refreshTransformUniform(material.thicknessMap, uniforms.thicknessMapTransform);
                }

                uniforms.attenuationDistance.value = material.attenuationDistance;
                (uniforms.attenuationColor.value as Color).Copy(material.attenuationColor);

            }

            uniforms.specularIntensity.value = material.specularIntensity;
            (uniforms.specularColor.value as Color).Copy(material.specularColor);

            if (material.specularColorMap != null)
            {
                uniforms.specularColorMap.value = material.specularColorMap;
                refreshTransformUniform(material.specularColorMap, uniforms.specularColorMapTransform);
            }

            if (material.specularIntensityMap != null)
            {

                uniforms.specularIntensityMap.value = material.specularIntensityMap;

                refreshTransformUniform(material.specularIntensityMap, uniforms.specularIntensityMapTransform);

            }
        }

        public void refreshUniformsMatcap(Uniforms uniforms, MeshMatcapMaterial material)
        {

            if (material.matcap != null)
            {

                uniforms.matcap.value = material.matcap;

            }
        }

        public void refreshUniformsDistance(Uniforms uniforms, MeshDistanceMaterial material)
        {
            var light = properties.get(material).light;

            (uniforms.referencePosition.value as Vector3).SetFromMatrixPosition(light.matrixWorld);
            uniforms.nearDistance.value = light.shadow.camera.near;
            uniforms.farDistance.value = light.shadow.camera.far;

        }
    }
}
